package cn.jtfadmin.base.data.jpa.autoconfig;


import cn.jtfadmin.base.data.jpa.service.DefaultJpaService;
import cn.jtfadmin.base.data.jpa.service.DefaultJpaTreeService;
import cn.jtfadmin.base.lang.common.domain.dto.BaseExcelModalDTO;
import cn.jtfadmin.base.lang.common.domain.dto.BaseTreeExcelModalDTO;
import cn.jtfadmin.base.lang.common.domain.dto.EditPDTO;
import cn.jtfadmin.base.lang.common.domain.dto.TreeEntityRequiredPDTO;
import cn.jtfadmin.base.lang.common.domain.entity.IdEntity;
import cn.jtfadmin.base.lang.common.domain.entity.TreeEntity;
import cn.jtfadmin.base.lang.common.service.BaseService;
import cn.jtfadmin.base.lang.common.service.BaseTreeService;
import cn.jtfadmin.base.lang.common.utils.DynamicCompilerUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.BeanFactory;
import org.springframework.beans.factory.BeanFactoryAware;
import org.springframework.beans.factory.config.*;
import org.springframework.beans.factory.support.*;
import org.springframework.boot.autoconfigure.AutoConfigurationPackages;
import org.springframework.boot.autoconfigure.domain.EntityScanPackages;
import org.springframework.context.EnvironmentAware;
import org.springframework.context.ResourceLoaderAware;
import org.springframework.context.annotation.ClassPathScanningCandidateComponentProvider;
import org.springframework.core.ResolvableType;
import org.springframework.core.env.Environment;
import org.springframework.core.io.ResourceLoader;
import org.springframework.core.type.AnnotationMetadata;
import org.springframework.core.type.filter.AnnotationTypeFilter;
import org.springframework.data.jpa.repository.config.EnableJpaRepositories;
import org.springframework.data.jpa.repository.config.JpaRepositoryConfigExtension;
import org.springframework.data.jpa.repository.support.JpaRepositoryImplementation;
import org.springframework.data.jpa.repository.support.SimpleJpaRepository;
import org.springframework.data.repository.config.AnnotationRepositoryConfigurationSource;
import org.springframework.data.repository.config.RepositoryConfigurationDelegate;
import org.springframework.data.util.Streamable;

import org.springframework.stereotype.Service;
import org.springframework.util.ClassUtils;
import org.springframework.util.StreamUtils;

import javax.persistence.Entity;
import java.io.InputStream;
import java.nio.charset.StandardCharsets;
import java.util.*;
import java.util.stream.Collectors;

/**
 * 自动配置
 * @author jtf
 */
@Service
public class BaseDataJpaAutoConfiguration implements BeanDefinitionRegistryPostProcessor, BeanFactoryAware, ResourceLoaderAware, EnvironmentAware {


    @EnableJpaRepositories(basePackages = "cn.jtfadmin.base.data.jpa.repository")
    private static class EnableJpaRepositoriesConfiguration {

    }

    private Logger logger = LoggerFactory.getLogger(getClass());


    private ResourceLoader resourceLoader;

    private BeanFactory beanFactory;

    private Environment environment;




    protected  List<BeanDefinition> getEntityBeanDefinition(){
        List<String> packages = new ArrayList<>();
        packages.addAll(EntityScanPackages.get(beanFactory).getPackageNames());
        if (packages.isEmpty() && AutoConfigurationPackages.has(beanFactory)) {
            packages .addAll( AutoConfigurationPackages.get(beanFactory));
        }
        ClassPathScanningCandidateComponentProvider scanner = new ClassPathScanningCandidateComponentProvider(false);
        scanner.addIncludeFilter(new AnnotationTypeFilter(Entity.class));
        scanner.setEnvironment(environment);
        scanner.setResourceLoader(resourceLoader);
        Streamable<BeanDefinition> candidates = Streamable.of(() -> packages.stream()//
                .flatMap(it -> scanner.findCandidateComponents(it).stream()));
        return candidates.toList();
    }


    @Override
    public void setEnvironment(Environment environment) {
        this.environment = environment;
    }


    protected void registryRepository(List<BeanDefinition> candidates, BeanDefinitionRegistry registry){
        if(! (this.beanFactory instanceof ConfigurableListableBeanFactory)) {
            return;

        }

        ConfigurableListableBeanFactory configurableListableBeanFactory = (ConfigurableListableBeanFactory) this.beanFactory;
        List<BeanDefinition> nrbds = candidates.stream().filter(
                t -> {
                    try {
                        return configurableListableBeanFactory.getBeanNamesForType(ResolvableType.forClassWithGenerics(JpaRepositoryImplementation.class, resourceLoader
                                .getClassLoader().loadClass(t.getBeanClassName()), String.class)).length == 0;
                    } catch (ClassNotFoundException e) {
                        throw new RuntimeException(e);
                    }
                }
        ).collect(Collectors.toList());
        if (nrbds.isEmpty()) {
            return;
        }
        String repository = "";
        try (InputStream repStream = resourceLoader.getResource("classpath:temp/repository.temp").getInputStream();
        ) {
            repository = StreamUtils.copyToString(repStream, StandardCharsets.UTF_8);
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
        for (BeanDefinition nrbd : nrbds) {
            String beanClassName = nrbd.getBeanClassName();
            Class<?> clazz = null;
            try {
                clazz = Class.forName(beanClassName);
            } catch (ClassNotFoundException e) {
                throw new RuntimeException(e);
            }
            String entityName = clazz.getName();
            String entitySimpleName = clazz.getSimpleName();
            String repositoryName = "cn.jtfadmin.base.data.jpa.repository." + entitySimpleName + "Repository";
            String source = repository.replace("[[EntitySimpleName]]", entitySimpleName).replace("[[EntityName]]", entityName);
            DynamicCompilerUtils.compilerToFile(repositoryName, source, ExtJpaRepsitoryClassPathResourceLoaderInitializer.extClassDir);
        }
        JpaRepositoryConfigExtension extension = new JpaRepositoryConfigExtension();
        AnnotationRepositoryConfigurationSource configurationSource = new AnnotationRepositoryConfigurationSource(AnnotationMetadata.introspect(EnableJpaRepositoriesConfiguration.class),
                EnableJpaRepositories.class, resourceLoader, environment, registry, null);
        RepositoryConfigurationDelegate delegate = new RepositoryConfigurationDelegate(
                configurationSource, this.resourceLoader, this.environment);
        delegate.registerRepositoriesIn(registry, extension);


    }

    @Override
    public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) throws BeansException {
        List<BeanDefinition> candidates = getEntityBeanDefinition();
        registryRepository(candidates, registry);
        registryService(candidates, registry);

    }

    protected void registryService(List<BeanDefinition> candidates, BeanDefinitionRegistry registry) {
        candidates = new ArrayList<>(candidates);
        if(! (this.beanFactory instanceof ConfigurableListableBeanFactory)) {
            return;

        }
        ConfigurableListableBeanFactory configurableListableBeanFactory = (ConfigurableListableBeanFactory) this.beanFactory;
        List<BeanDefinition> treeEntityBeanDefinitions = candidates.stream().filter(
                t->{
                    Class clazz = null;
                    try {
                        clazz = resourceLoader.getClassLoader().loadClass(t.getBeanClassName());
                    } catch (ClassNotFoundException e) {
                        throw new RuntimeException(e);
                    }
                    return TreeEntity.class.isAssignableFrom(clazz) && configurableListableBeanFactory.getBeanNamesForType(ResolvableType.forClassWithGenerics(BaseTreeService.class, clazz)).length == 0;
                }
        ).collect(Collectors.toList());
        candidates.removeAll(treeEntityBeanDefinitions);
        if(!treeEntityBeanDefinitions.isEmpty()){
            String treeServiceSource = "";
            try (InputStream repStream = resourceLoader.getResource("classpath:temp/treeService.temp").getInputStream();
            ) {
                treeServiceSource = StreamUtils.copyToString(repStream, StandardCharsets.UTF_8);
            } catch (Exception e) {
                throw new RuntimeException(e);
            }
            for (BeanDefinition nrbd : treeEntityBeanDefinitions) {
                String beanClassName = nrbd.getBeanClassName();
                Class<?> clazz = null;
                try {
                    clazz = Class.forName(beanClassName);
                } catch (ClassNotFoundException e) {
                    throw new RuntimeException(e);
                }
                String entityName = clazz.getName();
                String entitySimpleName = clazz.getSimpleName();
                String serviceName = "cn.jtfadmin.base.data.jpa.service.impl." + entitySimpleName + "Service";
                String source = treeServiceSource.replace("[[EntitySimpleName]]", entitySimpleName).replace("[[EntityName]]", entityName);
                DynamicCompilerUtils.compilerToFile(serviceName, source, ExtJpaRepsitoryClassPathResourceLoaderInitializer.extClassDir);
            }


        }
        List<BeanDefinition> baseEntityBeanDefinitions = candidates.stream().filter(
                t->{
                    Class clazz = null;
                    try {
                        clazz = resourceLoader.getClassLoader().loadClass(t.getBeanClassName());
                    } catch (ClassNotFoundException e) {
                        throw new RuntimeException(e);
                    }
                    return IdEntity.class.isAssignableFrom(clazz) && configurableListableBeanFactory.getBeanNamesForType(ResolvableType.forClassWithGenerics(BaseService.class, clazz)).length == 0;
                }
        ).collect(Collectors.toList());

        if(!baseEntityBeanDefinitions.isEmpty()){
            String baseServiceSource = "";
            try (InputStream repStream = resourceLoader.getResource("classpath:temp/service.temp").getInputStream();
            ) {
                baseServiceSource = StreamUtils.copyToString(repStream, StandardCharsets.UTF_8);
            } catch (Exception e) {
                throw new RuntimeException(e);
            }
            for (BeanDefinition nrbd : baseEntityBeanDefinitions) {
                String beanClassName = nrbd.getBeanClassName();
                Class<?> clazz = null;
                try {
                    clazz = Class.forName(beanClassName);
                } catch (ClassNotFoundException e) {
                    throw new RuntimeException(e);
                }
                String entityName = clazz.getName();
                String entitySimpleName = clazz.getSimpleName();
                String serviceName = "cn.jtfadmin.base.data.jpa.service.impl." + entitySimpleName + "Service";
                String source = baseServiceSource.replace("[[EntitySimpleName]]", entitySimpleName).replace("[[EntityName]]", entityName);
                DynamicCompilerUtils.compilerToFile(serviceName, source, ExtJpaRepsitoryClassPathResourceLoaderInitializer.extClassDir);
            }


        }

        ClassPathScanningCandidateComponentProvider scanner = new ClassPathScanningCandidateComponentProvider(false);
        scanner.addIncludeFilter(new AnnotationTypeFilter(Service.class));
        scanner.setEnvironment(environment);
        scanner.setResourceLoader(resourceLoader);
        Set<BeanDefinition> candidateComponents = scanner.findCandidateComponents("cn.jtfadmin.base.data.jpa.service.impl");
        for (BeanDefinition candidateComponent : candidateComponents) {
            registry.registerBeanDefinition(BeanDefinitionReaderUtils.generateBeanName(candidateComponent, registry), candidateComponent);
        }

    }

    @Override
    public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException {

    }





    @Override
    public void setResourceLoader(ResourceLoader resourceLoader) {
        this.resourceLoader = resourceLoader;
    }

    @Override
    public void setBeanFactory(BeanFactory beanFactory) throws BeansException {
        this.beanFactory = beanFactory;
    }


}
